/**
 *   Copyright (C) Sangfor Corporation. All rights reserved.

 *   \file     socket_widget.c
 *   \author   RainVan(Yunfeng.Xiao)
 *   \date     Sep 2011
 *   \version  1.0.0
 *   \brief    Implementation of socket widget
 */

#include "socket_widget.h"
#include <fcntl.h>
#include <sys/ioctl.h>

__inline static
void memcpy_8words(void *dst, const void *src)
{
    uint32_t *d = (uint32_t*)dst;
    const uint32_t *s = (const uint32_t*)src;

    *d++ = *s++;  /* 01-04 bytes */
    *d++ = *s++;  /* 05-08 bytes */
    *d++ = *s++;  /* 09-12 bytes */
    *d   = *s;    /* 13-16 bytes */
}

__inline static
int socket_stoip(const struct sockaddr_storage *saddr, 
                 ip_address_t *addr, uint16_t *port)
{
    int af = saddr->ss_family;

    if (af == AF_INET)
    {
        const struct sockaddr_in *sa = (const struct sockaddr_in*)saddr;
        if (addr)
        {
            ip_address_set_ip(addr, sa->sin_addr.s_addr);
        }
        if (port)
        {
            *port = sa->sin_port;
        }
    }
    else if (af == AF_INET6)
    {
        const struct sockaddr_in6 *sa = (const struct sockaddr_in6*)saddr;
        if (addr)
        {
            ip_address_set_ip6(addr, (const uint32_t*)(&sa->sin6_addr));
        }
        if (port)
        {
            *port = sa->sin6_port;
        }
    }
    else
    {
        /* not support */
        errno = EAFNOSUPPORT;
        return -1;
    }
    return 0;
}

int socket_set_block(socket_t fd, long sec, long usec)
{
    struct timeval tv = { 0 };

    int opts = fcntl(fd, F_GETFL);
    if (opts == -1)
    {
        return -1;
    }

    /* non-block? */
    if ((opts & O_NONBLOCK) == O_NONBLOCK)
    {
        /* remove non-block flags */
        opts &= ~O_NONBLOCK;
        if (fcntl(fd, F_SETFL, opts) == -1)
        {
            return -1;
        }
    }

    /* set timeout of send/recv */
    tv.tv_sec = sec;
    tv.tv_usec = usec;
    if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) != 0)
    {
        return -1;
    }
    if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0)
    {
        return -1;
    }

    /* success */
    return 0;
}

int socket_set_nonblock(socket_t fd)
{
    int opts = fcntl(fd, F_GETFL);
    if (opts == -1)
    {
        return -1;
    }

    /* non-block? */
    if ((opts & O_NONBLOCK) != O_NONBLOCK)
    {
        opts |= O_NONBLOCK;
        if (fcntl(fd, F_SETFL, opts) == -1)
        {
            return -1;
        }
    }

    /* success */
    return 0;
}

__inline static
int socket_set_reuseaddr(socket_t fd)
{
    int reuse = 1;
    return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
}

__inline static
socket_t socket_tcp_common(const ip_address_t *addr, uint16_t port, int reuse)
{
    socket_t fd = -1;
    int af = 0;

    af = ip_address_family(addr);
    if (af != AF_INET && af != AF_INET6)
    {
        errno = EAFNOSUPPORT;
        return -1;
    }

    fd = socket(af, SOCK_STREAM, IPPROTO_TCP);
    if (fd == -1)
    {
        return -1;
    }

    if (reuse)
    {
        /* IP address reuse */
        if (socket_set_reuseaddr(fd) != 0)
        {
            socket_close(fd);
            return -1;
        }
    }

    if (socket_bind(fd, addr, port) != 0)
    {
        socket_close(fd);
        return -1;
    }
    return fd;
}

socket_t socket_tcp_server(const ip_address_t *addr, uint16_t port, int reuse)
{
    socket_t fd = -1;

    SOCKET_ASSERT(addr);

    fd = socket_tcp_common(addr, port, reuse);
    if (fd == -1)
    {
        return -1;
    }

    if (socket_listen(fd) != 0)
    {
        socket_close(fd);
        return -1;
    }

    if (socket_set_nonblock(fd) != 0)
    {
        socket_close(fd);
        return -1;
    }
    return fd;
}

socket_t socket_tcp_client(const ip_address_t *addr, uint16_t port)
{
    SOCKET_ASSERT(addr);
    return socket_tcp_common(addr, port, 0);
}

__inline static
socket_t socket_udp_common(const ip_address_t *addr, uint16_t port, int reuse)
{
    socket_t fd = -1;
    int af = 0;

    af = ip_address_family(addr);
    if (af != AF_INET && af != AF_INET6)
    {
        errno = EAFNOSUPPORT;
        return -1;
    }

    fd = socket(af, SOCK_DGRAM, IPPROTO_UDP);
    if (fd == -1)
    {
        return -1;
    }

    if (reuse)
    {
        /* IP address reuse */
        if (socket_set_reuseaddr(fd) != 0)
        {
            socket_close(fd);
            return -1;
        }
    }

    if (socket_bind(fd, addr, port) != 0)
    {
        socket_close(fd);
        return -1;
    }
    return fd;
}

socket_t socket_udp_server(const ip_address_t *addr, uint16_t port, int reuse)
{
    socket_t fd = -1;

    SOCKET_ASSERT(addr);

    fd = socket_udp_common(addr, port, reuse);
    if (fd == -1)
    {
        return -1;
    }

    if (socket_set_nonblock(fd) != 0)
    {
        socket_close(fd);
        return -1;
    }
    return fd;
}

socket_t socket_udp_client(const ip_address_t *addr, uint16_t port)
{
    SOCKET_ASSERT(addr);
    return socket_udp_common(addr, port, 0);
}

socket_t socket_accept(socket_t fd, ip_address_t *addr, uint16_t *port)
{
    socket_t ret = -1;

    if (addr || port)
    {
        struct sockaddr_storage saddr = { 0 };
        socklen_t alen = sizeof(saddr);

        /* accepting */
        ret = accept(fd, (struct sockaddr*)&saddr, &alen);
        while (ret == -1 && errno == EINTR)
        {
			ret = accept(fd, (struct sockaddr*)&saddr, &alen);
        }

        if (ret != -1)
        {
            if (socket_stoip(&saddr, addr, port) != 0)
            {
            	socket_close(ret); 
                return -1;
            }
        }
    }
    else
    {
        /* accepting */
        ret = accept(fd, NULL, NULL);
        while (ret == -1 && errno == EINTR)
        {
            ret = accept(fd, NULL, NULL);
        }
    }
    return ret;
}

socket_t socket_accept_n(socket_t fd, ip_address_t *addr, 
                         uint16_t *port, size_t n)
{
    SOCKET_ASSERT(n > 0);

    while (n--)
    {
        socket_t ret = socket_accept(fd, addr, port);
        if (ret != -1 || errno != EAGAIN)
        {
            return ret;
        }
    }
    return -1;
}

int socket_bind(socket_t fd, const ip_address_t *addr, uint16_t port)
{
    int af = 0;

    SOCKET_ASSERT(addr);

    af = ip_address_family(addr);
    if (af == AF_INET)
    {
        struct sockaddr_in saddr = { 0 };
        saddr.sin_family = AF_INET;
        saddr.sin_port = port;
        saddr.sin_addr.s_addr = ip_address_get_ip(addr);

        if (bind(fd, (struct sockaddr*)&saddr, sizeof(saddr)) != 0)
        {
            return -1;
        }
    }
    else if (af == AF_INET6)
    {
        struct sockaddr_in6 saddr = { 0 };
        saddr.sin6_family = AF_INET6;
        saddr.sin6_port = port;

        /* assign IP address */
        memcpy_8words(&saddr.sin6_addr, ip_address_get_ip6(addr));

        if (bind(fd, (struct sockaddr*)&saddr, sizeof(saddr) != 0))
        {
            return -1;
        }
    }
    else
    {
        /* not support */
        errno = EAFNOSUPPORT;
        return -1;
    }
    /* success */
    return 0;
}

int socket_connect(socket_t fd, const ip_address_t *addr, uint16_t port)
{
    int ret = -1;
    int af = 0;

    SOCKET_ASSERT(addr);

    af = ip_address_family(addr);
    if (af == AF_INET)
    {
        struct sockaddr_in saddr = { 0 };
        saddr.sin_family = AF_INET;
        saddr.sin_port = port;
        saddr.sin_addr.s_addr = ip_address_get_ip(addr);

        /* connecting */
        ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));
        while (ret == -1 && errno == EINTR)
        {
            ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));
        }
    }
    else if (af == AF_INET6)
    {
        struct sockaddr_in6 saddr = { 0 };
        saddr.sin6_family = AF_INET6;
        saddr.sin6_port = port;

        /* assign IP address */
        memcpy_8words(&saddr.sin6_addr, ip_address_get_ip6(addr));

        /* connecting */
        ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));
        while (ret == -1 && errno == EINTR)
        {
            ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));
        }
    }
    else
    {
        /* not support */
        errno = EAFNOSUPPORT;
    }
    return ret;
}

int socket_connect_n(socket_t fd, const ip_address_t *addr, 
                     uint16_t port, size_t n)
{
    SOCKET_ASSERT(n > 0);

    while (n--)
    {
        int ret = socket_connect(fd, addr, port);
        if (ret != -1 || errno != EAGAIN)
        {
            return ret;
        }
    }
    return -1;
}

ssize_t socket_recvfrom(socket_t fd, void *buf, size_t len, 
                        ip_address_t *addr, uint16_t *port)
{
    ssize_t ret = -1;

    SOCKET_ASSERT(buf);

    if (addr || port)
    {
        struct sockaddr_storage saddr = { 0 };
        socklen_t alen = sizeof(saddr);

        /* receiving data */
        ret = recvfrom(fd, buf, len, 0, (struct sockaddr*)&saddr, &alen);
        while (ret == -1 && errno == EINTR)
        {
            ret = recvfrom(fd, buf, len, 0, (struct sockaddr*)&saddr, &alen);
        }

        if (ret != -1)
        {
            if (socket_stoip(&saddr, addr, port) != 0)
            {
                return (ssize_t)(-1);
            }
        }
    }
    else
    {
        /* receiving data */
        ret = recvfrom(fd, buf, len, 0, NULL, NULL);
        while (ret == -1 && errno == EINTR)
        {
            ret = recvfrom(fd, buf, len, 0, NULL, NULL);
        }
    }
    return ret;
}

ssize_t socket_sendto(socket_t fd, const void *buf, size_t len, 
                      const ip_address_t *addr, uint16_t port)
{
    ssize_t ret = -1;
    int af = 0;

    SOCKET_ASSERT(buf && addr);

    af = ip_address_family(addr);
    if (af == AF_INET)
    {
        struct sockaddr_in saddr = { 0 };
        saddr.sin_family = AF_INET;
        saddr.sin_port = port;
        saddr.sin_addr.s_addr = ip_address_get_ip(addr);

        /* sending data */
        ret = sendto(
            fd, buf, len, 0, 
            (struct sockaddr*)&saddr, sizeof(saddr)
            );

        while (ret == -1 && errno == EINTR)
        {
            ret = sendto(
                fd, buf, len, 0, 
                (struct sockaddr*)&saddr, sizeof(saddr)
                );
        }
    }
    else if (af == AF_INET6)
    {
        struct sockaddr_in6 saddr = { 0 };
        saddr.sin6_family = AF_INET6;
        saddr.sin6_port = port;

        /* assign IP address */
        memcpy_8words(&saddr.sin6_addr, ip_address_get_ip6(addr));

        /* sending data */
        ret = sendto(
            fd, buf, len, 0, 
            (struct sockaddr*)&saddr, sizeof(saddr)
            );

        while (ret == -1 && errno == EINTR)
        {
            ret = sendto(
                fd, buf, len, 0, 
                (struct sockaddr*)&saddr, sizeof(saddr)
                );
        }
    }
    else
    {
        /* not support */
        errno = EAFNOSUPPORT;
    }
    return ret;
}

ssize_t socket_recvfrom_n(socket_t fd, void *buf, size_t len, 
                          ip_address_t *addr, uint16_t *port, size_t n)
{
    SOCKET_ASSERT(n > 0);

    while (n--)
    {
        ssize_t ret = socket_recvfrom(fd, buf, len, addr, port);
        if (ret != -1 || errno != EAGAIN)
        {
            return ret;
        }
    }
    return (ssize_t)(-1);
}

ssize_t socket_sendto_n(socket_t fd, const void *buf, size_t len, 
                        const ip_address_t *addr, uint16_t port, size_t n)
{
    SOCKET_ASSERT(n > 0);

    while (n--)
    {
        ssize_t ret = socket_sendto(fd, buf, len, addr, port);
        if (ret != -1 || errno != EAGAIN)
        {
            return ret;
        }
    }
    return (ssize_t)(-1);
}

int socket_get_sockname(socket_t fd, ip_address_t *addr, uint16_t *port)
{
    struct sockaddr_storage saddr = { 0 };
    socklen_t alen = sizeof(saddr);

    if (getsockname(fd, (struct sockaddr*)&saddr, &alen) != 0)
    {
        return -1;
    }

    if (socket_stoip(&saddr, addr, port) != 0)
    {
        return -1;
    }
    return 0;
}

int socket_get_peername(socket_t fd, ip_address_t *addr, uint16_t *port)
{
    struct sockaddr_storage saddr = { 0 };
    socklen_t alen = sizeof(saddr);

    if (getpeername(fd, (struct sockaddr*)&saddr, &alen) != 0)
    {
        return -1;
    }

    if (socket_stoip(&saddr, addr, port) != 0)
    {
        return -1;
    }
    return 0;
}

uint16_t socket_get_family(socket_t fd)
{
    struct sockaddr_storage saddr = { 0 };
    socklen_t alen = sizeof(saddr);

    if (getsockname(fd, (struct sockaddr*)&saddr, &alen) != 0)
    {
        /* unspecified */
        return (uint16_t)0;
    }

    return saddr.ss_family;
}

int socket_listen(socket_t fd)
{
    return listen(fd, SOMAXCONN);
}

int socket_reset(socket_t fd)
{
    struct linger opt = { 0 };
    opt.l_onoff = 1;
    opt.l_linger = 0;

    /* 1. set linger */
    if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &opt, sizeof(opt)) != 0)
    {
        return -1;
    }

    /* 2. close socket */
    return socket_close(fd);
}

int socket_close(socket_t fd)
{
    int ret = close(fd);
    while (ret == -1 && errno == EINTR)
    {
        ret = close(fd); /* parasoft-suppress BD-RES-FREE "no error" */
    }
    return ret;
}

int socket_shutdown(socket_t fd)
{
    return shutdown(fd, SHUT_RDWR);
}

ssize_t socket_read(socket_t fd, void *buf, size_t len)
{
    ssize_t ret = -1;

    SOCKET_ASSERT(buf);

    ret = read(fd, buf, len);
    while (ret == -1 && errno == EINTR)
    {
        ret = read(fd, buf, len);
    }
    return ret;
}

ssize_t socket_write(socket_t fd, const void *buf, size_t len)
{
    ssize_t ret = -1;

    SOCKET_ASSERT(buf);

    ret = write(fd, buf, len);
    while (ret == -1 && errno == EINTR)
    {
        ret = write(fd, buf, len);
    }
    return ret;
}

ssize_t socket_read_n(socket_t fd, void *buf, size_t len)
{
    char *ptr, *end;

    SOCKET_ASSERT(buf);

    ptr = (char*)buf;
    end = ptr + len;
    while (ptr != end)
    {
        ssize_t ret = read(fd, ptr, end - ptr);
        if (ret <= 0)
        {
            if (ret == -1 && errno == EINTR)
                continue;
            else
                break;
        }
        ptr += ret;
    }
    return (ssize_t)(ptr - (char*)buf);
}

ssize_t socket_write_n(socket_t fd, const void *buf, size_t len)
{
    const char *ptr, *end;

    SOCKET_ASSERT(buf);

    ptr = (const char*)buf;
    end = ptr + len;
    while (ptr != end)
    {
        ssize_t ret = write(fd, ptr, end - ptr);
        if (ret <= 0)
        {
            if (ret == -1 && errno == EINTR)
                continue;
            else
                break;
        }
        ptr += ret;
    }
    return (ssize_t)(ptr - (const char*)buf);
}

__inline static
int socket_unix_stop(const struct sockaddr_un *saddr, char *path, size_t size)
{
    if (strlen(saddr->sun_path) >= size) /* parasoft-suppress BD-PB-OVERFNZT "no error" */
    {
        errno = ENOSPC;
        return -1;
    }

    /* It's safe here. */
    strncpy(path, saddr->sun_path, size);

    /* success */
    return 0;
}

socket_t socket_unix_udp_client(const char *path)
{
    socket_t fd = socket(PF_UNIX, SOCK_DGRAM, 0);

    if (fd != -1 && path)
    {
        if (socket_unix_bind(fd, path) != 0)
        {
            socket_close(fd);
            return -1;
        }
    }
    return fd;
}

socket_t socket_unix_udp_server(const char *path, int reuse)
{
    socket_t fd = -1;

    SOCKET_ASSERT(path);

    fd = socket(PF_UNIX, SOCK_DGRAM, 0);
    if (fd != -1)
    {
        if (reuse)
        {
            /* IP address reuse */
            if (socket_set_reuseaddr(fd) != 0)
            {
                socket_close(fd);
                return -1;
            }
        }

        if (socket_unix_bind(fd, path) != 0)
        {
            socket_close(fd);
            return -1;
        }

        if (socket_set_nonblock(fd) != 0)
        {
            socket_close(fd);
            return -1;
        }
    }
    return fd;
}

socket_t socket_unix_tcp_client(const char *path)
{
    socket_t fd = socket(PF_UNIX, SOCK_STREAM, 0);

    if (fd != -1 && path)
    {
        if (socket_unix_bind(fd, path) != 0)
        {
            socket_close(fd);
            return -1;
        }
    }
    return fd;
}

socket_t socket_unix_tcp_server(const char *path, int reuse)
{
    socket_t fd = -1;

    SOCKET_ASSERT(path);

    fd = socket(PF_UNIX, SOCK_STREAM, 0);
    if (fd != -1)
    {
        if (reuse)
        {
            /* IP address reuse */
            if (socket_set_reuseaddr(fd) != 0)
            {
                socket_close(fd);
                return -1;
            }
        }

        if (socket_unix_bind(fd, path) != 0)
        {
            socket_close(fd);
            return -1;
        }

        if (socket_listen(fd) != 0)
        {
            socket_close(fd);
            return -1;
        }

        if (socket_set_nonblock(fd) != 0)
        {
            socket_close(fd);
            return -1;
        }
    }
    return fd;
}

socket_t socket_unix_accept(socket_t fd, char *path, size_t size)
{
    socket_t ret = -1;

    if (path && size)
    {
        struct sockaddr_un saddr = { 0 };
        socklen_t alen = sizeof(saddr);

        /* accepting */
        ret = accept(fd, (struct sockaddr*)&saddr, &alen);
        while (ret == -1 && errno == EINTR)
        {
            ret = accept(fd, (struct sockaddr*)&saddr, &alen);
        }

        if (ret != -1)
        {
            if (socket_unix_stop(&saddr, path, size) != 0)
            {
            	socket_close(ret); 
                return -1;
            }
        }
    }
    else
    {
        /* accepting */
        ret = accept(fd, NULL, NULL);
        while (ret == -1 && errno == EINTR)
        {
			ret = accept(fd, NULL, NULL);
        }
    }
    return ret;
}

socket_t socket_unix_accept_n(socket_t fd, char *path, size_t size, size_t n)
{
    SOCKET_ASSERT(n > 0);

    while (n--)
    {
        socket_t ret = socket_unix_accept(fd, path, size);
        if (ret != -1 || errno != EAGAIN)
        {
            return ret;
        }
    }
    return -1;
}

int socket_unix_bind(socket_t fd, const char *path)
{
    struct sockaddr_un saddr = { 0 };

    SOCKET_ASSERT(path);

    /* assign path */
    saddr.sun_family = AF_UNIX;
    strncpy(saddr.sun_path, path, sizeof(saddr.sun_path)-1);

    /* unlink file first */
    unlink(path);

    if (bind(fd, (struct sockaddr*)&saddr, SUN_LEN(&saddr)) != 0)
    {
        return -1;
    }
    return 0;
}

int socket_unix_connect(socket_t fd, const char *path)
{
    int ret = -1;
    struct sockaddr_un saddr = { 0 };

    SOCKET_ASSERT(path);

    /* assign path */
    saddr.sun_family = AF_UNIX;
    strncpy(saddr.sun_path, path, sizeof(saddr.sun_path)-1);

    /* connecting */
    ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));
    while (ret == -1 && errno == EINTR)
    {
        ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));
    }

    return ret;
}

int socket_unix_connect_timeout(socket_t fd, const char *path, int second)
{
    int ret = -1;
	int noblock = 1;
    struct sockaddr_un saddr = { 0 };

    SOCKET_ASSERT(path);

    /* assign path */
    saddr.sun_family = AF_UNIX;
    strncpy(saddr.sun_path, path, sizeof(saddr.sun_path)-1);
	
	ioctl(fd, FIONBIO, noblock);

    /* connecting */
    ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));
	if (ret < 0) {
		struct timeval tm;
		fd_set set;
		if (errno != EINPROGRESS) {
			return -1;
		}
		tm.tv_sec = second;
		tm.tv_usec = 0;
		FD_ZERO(&set);
		FD_SET(fd, &set);
		if (select(fd + 1, NULL, &set, NULL, &tm) > 0) {
			noblock = 0;
			ioctl(fd, FIONBIO, noblock);
			return 0;
		}
		return -1;	
	}

	noblock = 0;
	ioctl(fd, FIONBIO, noblock);

    return ret;
}

int socket_unix_connect_n(socket_t fd, const char *path, size_t n)
{
    SOCKET_ASSERT(n > 0);

    while (n--)
    {
        int ret = socket_unix_connect(fd, path);
        if (ret != -1 || errno != EAGAIN)
        {
            return ret;
        }
    }
    return -1;
}

ssize_t socket_unix_recvfrom(socket_t fd, void *buf, size_t len, 
                             char *path, size_t size)
{
    ssize_t ret = -1;

    SOCKET_ASSERT(buf);

    if (path && size)
    {
        struct sockaddr_un saddr = { 0 };
        socklen_t alen = sizeof(saddr);

        /* receiving data */
        ret = recvfrom(fd, buf, len, 0, (struct sockaddr*)&saddr, &alen);
        while (ret == -1 && errno == EINTR)
        {
            ret = recvfrom(fd, buf, len, 0, (struct sockaddr*)&saddr, &alen);
        }

        if (ret != -1)
        {
            if (socket_unix_stop(&saddr, path, size) != 0)
            {
                return (ssize_t)(-1);
            }
        }
    }
    else
    {
        /* receiving data */
        ret = recvfrom(fd, buf, len, 0, NULL, NULL);
        while (ret == -1 && errno == EINTR)
        {
            ret = recvfrom(fd, buf, len, 0, NULL, NULL);
        }
    }
    return ret;
}

ssize_t socket_unix_recvfrom_n(socket_t fd, void *buf, size_t len, 
                               char *path, size_t size, size_t n)
{
    SOCKET_ASSERT(n > 0);

    while (n--)
    {
        ssize_t ret = socket_unix_recvfrom(fd, buf, len, path, size);
        if (ret != -1 || errno != EAGAIN)
        {
            return ret;
        }
    }
    return (ssize_t)(-1);
}

ssize_t socket_unix_sendto(socket_t fd, const void *buf, 
                           size_t len, const char *path)
{
    ssize_t ret = -1;
    struct sockaddr_un saddr = { 0 };

    SOCKET_ASSERT(path);

    /* assign path */
    saddr.sun_family = AF_UNIX;
    strncpy(saddr.sun_path, path, sizeof(saddr.sun_path)-1);

    /* sending data */
    ret = sendto(fd, buf, len, 0, (struct sockaddr*)&saddr, sizeof(saddr));
    while (ret == -1 && errno == EINTR)
    {
        ret = sendto(fd, buf, len, 0, (struct sockaddr*)&saddr, sizeof(saddr));
    }

    return ret;
}

ssize_t socket_unix_sendto_n(socket_t fd, const void *buf, size_t len, 
                             const char *path, size_t n)
{
    SOCKET_ASSERT(n > 0);

    while (n--)
    {
        ssize_t ret = socket_unix_sendto(fd, buf, len, path);
        if (ret != -1 || errno != EAGAIN)
        {
            return ret;
        }
    }
    return (ssize_t)(-1);
}

int socket_unix_get_sockname(socket_t fd, char *path, size_t size)
{
    struct sockaddr_un saddr = { 0 };
    socklen_t alen = sizeof(saddr);

    SOCKET_ASSERT(path);

    if (getsockname(fd, (struct sockaddr*)&saddr, &alen) != 0)
    {
        return -1;
    }

    if (socket_unix_stop(&saddr, path, size) != 0)
    {
        return -1;
    }
    return 0;
}

int socket_unix_get_peername(socket_t fd, char *path, size_t size)
{
    struct sockaddr_un saddr = { 0 };
    socklen_t alen = sizeof(saddr);

    SOCKET_ASSERT(path);

    if (getpeername(fd, (struct sockaddr*)&saddr, &alen) != 0)
    {
        return -1;
    }

    if (socket_unix_stop(&saddr, path, size) != 0)
    {
        return -1;
    }
    return 0;
}
